import { keywordCompare, truthy } from '@gnlab/utils'
import { OrderedMap } from 'immutable'
import { useStreams } from '@/lib/utils'
import services from '@/services'
import type {
  BaseExternalSearchConditions,
  BaseGroupSearchConditions,
  ExternalItemTag,
  GroupTag,
} from '@/services/contact-types'
import { ContactType, compareContacts } from '@/services/contacts'
import type { Contact } from '@/services/contacts'
import type { QywxInfo } from '@/services/qywxs'

/*
对于客户搜索，指定了 externalTags 时，调接口基于标签搜索，然后前端自己基于 keyword 过滤一遍
若未指定 externalTags，则调接口基于 keyword 搜索

群搜索也类似，不过没有 groupTags 时是直接取本地的完整群列表
然后无论有没有 groupTags 都是前端自己基于 keyword 过滤

员工没有标签，始终调接口基于 keyword 搜索
*/
interface SearchConditions {
  keyword: string // keyword 为空字符串代表不进行关键词搜索
  externalTags?: ExternalItemTag[] | null // 客户通过标签搜索客户
  groupTags?: GroupTag[] | null // 若指定则通过标签搜索群
}

export interface ContactData {
  contact: Contact
  ownerName: string
}

export function useSearchedContacts(type: ContactType, conditions: SearchConditions | null) {
  const srvExternalSearch = useMemo(() => services.externals.search, [])
  const srvInternalSearch = useMemo(() => services.internals.search, [])

  const [
    externalSearching,
    internalSearching,
    groupSearching,
    rawGroups,
    rawNotices,
    qywxs,
    focusedQywx,
  ] = useStreams(
    srvExternalSearch.searching,
    srvInternalSearch.searching,
    services.groups.searching,
    services.groups.list,
    services.notices.list,
    services.qywxs.qywxs,
    services.qywxs.focus.focused,
  )

  /**
   * 整理 conditions
   */
  let keyword = (conditions?.keyword.trim() ?? '') || null // 空字符串也转换为 null
  let keywordExact = false // 带引号的关键词执行“精确关键字匹配”，即只有名字完全相同才算匹配。目前后端不支持此功能，完全由前端实现，因此传给后端的是去掉引号的。
  if (keyword !== null && isQuoted(keyword)) {
    keyword = keyword.slice(1, -1)
    keywordExact = true
  }
  const externalTags =
    conditions?.externalTags?.length ?? 0 ? conditions!.externalTags!.map(t => t.id) : null
  const groupTags =
    conditions?.groupTags?.length ?? 0 ? conditions!.groupTags!.map(t => t.id) : null
  const wxids = useMemo(() => (focusedQywx ? [focusedQywx.wxid] : undefined), [focusedQywx])
  // 对于 external 和 group，keyword 和 tag 都没有值代表 conditions 为空
  // 对于其他类型，忽略 tag，只要 keyword 没有值就代表没有 conditions
  const hasConditions =
    type === ContactType.external
      ? truthy(keyword) || !!externalTags
      : type === ContactType.group
      ? truthy(keyword) || !!groupTags
      : truthy(keyword)

  /**
   * 触发搜索接口
   */
  const triggerExternalSearch = useCallback(() => {
    if (externalTags) {
      srvExternalSearch.byTags(externalTags, wxids)
    } else {
      srvExternalSearch.byKeyword(keyword!, wxids)
    }
  }, [keyword, externalTags, wxids])
  useEffect(() => {
    if (!truthy(keyword) && !externalTags) {
      // 当前未在进行搜索，清除之前的搜索结果
      if (externalSearching) srvExternalSearch.clear()
    } else {
      // 开启了新搜索或搜索条件变化
      // 若是在显示其他类型时开启或更新了搜索，则仅在进入当前类型时才触发接口查询
      if (
        type === ContactType.external &&
        (!externalSearching || !externalConditionsEquals(keyword, externalTags, externalSearching))
      ) {
        triggerExternalSearch()
      }
    }
  }, [type, externalSearching, keyword, externalTags, triggerExternalSearch])

  useEffect(() => {
    if (!groupTags) {
      // 当前未在进行搜索，清除之前的搜索结果
      if (groupSearching) services.groups.clearSearch()
    } else {
      // 开启了新搜索或搜索条件变化
      // 若是在显示其他类型时开启或更新了搜索，则仅在进入当前类型时才触发接口查询
      if (
        type === ContactType.group &&
        (!groupSearching || !compareGroupConditions(groupTags, groupSearching))
      ) {
        void services.groups.searchByTags(groupTags, wxids)
      }
    }
  }, [type, groupSearching, groupTags, wxids])

  useEffect(() => {
    if (!truthy(keyword)) {
      if (internalSearching) srvInternalSearch.clear()
    } else if (
      type === ContactType.internal &&
      (!internalSearching || keyword !== internalSearching.keyword)
    ) {
      srvInternalSearch.byKeyword(keyword, wxids)
    }
  }, [type, internalSearching, keyword, wxids])

  /**
   * 离开时清理搜索数据
   */
  useEffect(() => {
    return () => {
      srvExternalSearch.clear()
      srvInternalSearch.clear()
      services.groups.clearSearch()
    }
  }, [])

  const externalSearchNext = useCallback(async () => srvExternalSearch.next(), [])
  const internalSearchNext = useCallback(async () => srvExternalSearch.next(), [])

  /**
   * 生成搜索结果
   */
  const externals = useMemo(() => {
    if (type !== ContactType.external || !hasConditions) return []
    // 外部联系人在调接口查询时已指定了 wxid，不用再基于 focusedQywx 过滤一遍了
    return externalSearching?.items ? [...externalSearching.items.values()] : []
  }, [type, externalSearching, hasConditions])

  const groups = useMemo(() => {
    if (type !== ContactType.group || !hasConditions) return []
    const map = groupSearching
      ? groupSearching.items ?? OrderedMap()
      : focusedQywx
      ? rawGroups.filter(contact => contact.ownerWxid === focusedQywx.wxid)
      : rawGroups
    return [...map.values()]
  }, [type, rawGroups, groupSearching, focusedQywx, hasConditions])

  const internals = useMemo(() => {
    if (type !== ContactType.internal || !hasConditions) return []
    // 外部联系人在调接口查询时已指定了 wxid，不用再基于 focusedQywx 过滤一遍了
    const internals = internalSearching?.items ? [...internalSearching.items.values()] : []
    const combined = [...internals, ...rawNotices.values()] // 对于员工，也把 notices 包含进来
    return combined.sort(compareContacts)
  }, [type, internalSearching, rawNotices, hasConditions])

  const searchedContacts = useMemo(() => {
    let contacts: Contact[] =
      type === ContactType.external ? externals : type === ContactType.group ? groups : internals

    // 虽然客户通过关键词执行查询时，后端已基于关键词过滤过一遍，但因为前端还实现了引号精确匹配，所以依然需要前端再过滤一次
    if (truthy(keyword))
      contacts = contacts.filter(contact => filterContact(keyword!, contact, keywordExact))

    return contacts.map(ct => formatData(ct, qywxs))
  }, [type, keyword, keywordExact, externals, groups, internals, qywxs])

  return {
    contacts: searchedContacts,
    hasConditions,
    externalSearching,
    externalSearchNext,
    groupSearching,
    internalSearching,
    internalSearchNext,
    searchExternal: triggerExternalSearch, // 手动触发外部联系人搜索
  }
}

/**
 * 判断界面输入的搜索条件与服务中当前的是否一致，是否要开启新搜索
 */
function externalConditionsEquals(
  keyword: string | null,
  tagIds: string[] | null,
  b: BaseExternalSearchConditions,
) {
  if (tagIds) return 'tagIds' in b && JSON.stringify(tagIds) === JSON.stringify(b.tagIds)
  return 'keyword' in b && keyword === b.keyword
}
function compareGroupConditions(tagIds: number[] | null, b: BaseGroupSearchConditions) {
  return JSON.stringify(tagIds) === JSON.stringify(b.tagIds)
}

function isQuoted(text: string) {
  return /(^".+"$)|(^'.+')/.exec(text)
}

function filterContact(keyword: string, contact: Contact, exact: boolean) {
  const compare = exact
    ? (text: string) => keyword === text || `'${keyword}'` === text || `"${keyword}"` === text // 后两种为应对特殊情况，刚好名称本身就是由引号包裹的
    : (text: string) => keywordCompare(keyword, text)
  const textMatch =
    contact.type === ContactType.external
      ? exact
        ? compare(contact.name) || compare(contact.remark) || compare(contact.describe)
        : true
      : compare(contact.name) || compare(contact.remark)
  return textMatch || contact.userId === keyword
}

function formatData(contact: Contact, qywxs: OrderedMap<string, QywxInfo>): ContactData {
  return {
    contact,
    ownerName: qywxs.get(contact.ownerWxid)?.friendlyFullName ?? '',
  }
}
